//
//  TTWebService.m
//  Tint
//
//  Created by Kevin Wu on 2/29/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "TTWebService.h"

@interface TTWebService (Private)

- (NSString *)buildRequestAddressIfNeeded;
- (NSData *)buildRequestBodyIfNeeded;

+ (NSThread *)webServiceParseThread;
+ (void)webServiceParseThreadEntryPoint:(id)object;

@end



@implementation TTWebService



#pragma mark - Accessors

@synthesize serviceAddress = _serviceAddress;
@synthesize responseData = _responseData;
@synthesize request = _request;
@synthesize requestTimeoutInterval = _requestTimeoutInterval;
@synthesize requestMethod = _requestMethod;
@synthesize requestBody = _requestBody;

@synthesize parsedObject = _parsedObject;

@synthesize cacheTimeoutInterval = _cacheTimeoutInterval;
@synthesize cacheKey = _cacheKey;

@synthesize loading = _loading;
@synthesize cancelled = _cancelled;


#pragma mark - Memory

- (id)init {
    self = [super init];
    if (self) {
        [self setUp];
    }
    return self;
}

- (void)dealloc {
    TTRelease(_serviceAddress);
    TTRelease(_responseData);
    TTRelease(_request);
    _requestTimeoutInterval = 0.0;
    TTRelease(_requestMethod);
    TTRelease(_requestBody);
    
    TTRelease(_parsedObject);
    
    _cacheTimeoutInterval = 0.0;
    TTRelease(_cacheKey);
    
    _loading = NO;
    _cancelled = NO;
    
    
    TTRelease(_pageManagerMap);
    
    TTRelease(_parameters);
    
    _diskCache = nil;
    
    TTRelease(_delegates);
    [super dealloc];
}


#pragma mark - Public

static NSMutableDictionary *serviceMap = nil;

+ (id)serviceByTypeNumber:(NSUInteger)number {
    NSString *type = [NSString stringWithFormat:@"%d", number];
    return [self serviceByType:type];
}

+ (id)serviceByType:(NSString *)type {
    if (serviceMap == nil) serviceMap = [[NSMutableDictionary alloc] init];
    
    NSString *key = [NSString stringWithFormat:@"%@_%@", [self classString], type];
    id service = [serviceMap objectForKey:key];
    if (service == nil) {
        service = [[[self alloc] init] autorelease];
        [serviceMap setObject:service forKey:key];
    }
    return service;
}

+ (void)destroyServiceByNumber:(NSUInteger)number {
    NSString *type = [NSString stringWithFormat:@"%d", number];
    [self destroyServiceByType:type];
}

+ (void)destroyServiceByType:(NSString *)type {
    
    NSString *key = [NSString stringWithFormat:@"%@_%@", [self classString], type];
    [serviceMap removeObjectForKey:key];
    
}

+ (id)sharedObject {
    return nil;
}

- (void)setUp {
    _serviceAddress = nil;
    _responseData = nil;
    _request = nil;
    _requestTimeoutInterval = 0.0;
    _requestMethod = [[NSString alloc] initWithString:@"GET"];
    _requestBody = nil;
    
    _parsedObject = nil;
    
    _cacheTimeoutInterval = 0.0;
    _cacheKey = [[NSString alloc] initWithString:[self classString]];
    
    _loading = NO;
    _cancelled = NO;
    
    
    _pageManagerMap = nil;
    
    _parameters = nil;
    
    _diskCache = [TTDiskCache cacheByType:@"WebService"];
    
    _delegates = nil;
}


- (void)setParameterDictionary:(NSDictionary *)dict {
    TTRelease(_parameters);
    
    NSEnumerator *enumerator = [dict keyEnumerator];
    NSString *key = nil;
    while ((key = [enumerator nextObject])) {
        NSString *parameter = [dict objectForKey:key];
        [self addParameter:parameter forKey:key];
    }
}

- (void)addParameter:(NSString *)parameter forKey:(NSString *)key {
    if (_loading) return;
    if (_parameters == nil) _parameters = [[NSMutableDictionary alloc] init];
    
    [_parameters setObject:parameter forKeyIfNotNil:key];
}

- (void)addAllPropertiesAsParameter {
    NSArray *properties = [[self class] propertyNames];
    if (TTIsArrayWithItems(properties)) {
        [self addPropertiesAsParameter:[properties componentsJoinedByString:@","]];
    }
}

- (void)addAllPropertiesAsParameterExcept:(NSString *)propertyString {
    NSArray *tmp = [[propertyString componentsSeparatedByString:@","] nonEqualObjectsArray];
    NSMutableArray *exceptFields = [NSMutableArray array];
    for (NSString *property in tmp) {
        [exceptFields addObjectIfNotNil:[property stringByRemovingWhitespace]];
    }
    
    NSMutableArray *properties = [NSMutableArray arrayWithArray:[[self class] propertyNames]];
    [properties removeObjectsInArray:exceptFields];
    
    if (TTIsArrayWithItems(properties)) {
        [self addPropertiesAsParameter:[properties componentsJoinedByString:@","]];
    }
}

- (void)addPropertiesAsParameter:(NSString *)propertyString {
    
    if (_loading) return;
    if (_parameters == nil) _parameters = [[NSMutableDictionary alloc] init];
    
    NSArray *tmp = [propertyString componentsSeparatedByString:@","];
    
    NSMutableArray *properties = [NSMutableArray array];
    for (NSString *name in tmp) {
        [properties addObjectIfNotNil:[name stringByRemovingWhitespace]];
    }
    
    for (NSString *name in properties) {
        NSString *value = [self stringValueForProperty:name];
        [_parameters setObject:value forKeyIfNotNil:name];
    }
}

- (void)removeParameterForKey:(NSString *)key {
    [_parameters removeObjectForKey:key];
}

- (NSString *)parameterForKey:(NSString *)key {
    if (TTIsStringWithText(key)) {
        return [_parameters objectForKey:key];
    }
    return nil;
}

- (void)clearAllParameters {
    if (_loading) return;
    [_parameters removeAllObjects];
}


- (void)start {
    [self refreshWithCacheTimeoutInterval:0];
}

- (void)startWithCacheTimeoutInterval:(NSTimeInterval)timeoutInterval {
    if (_loading) return;
    
    if (![_diskCache hasCacheForKey:_cacheKey]) {
        [self refreshWithCacheTimeoutInterval:timeoutInterval];
    } else {
        [self webServiceAlreadyInCache];
//        [self webServiceDidStart];
//        [self webServiceWillFinish];
//        [self webServiceDidFinish];
    }
}

- (void)refreshWithCacheTimeoutInterval:(NSTimeInterval)timeoutInterval {
    if (_loading) return;
    
    _cancelled = NO;
    
    
    [_diskCache removeCacheForKey:_cacheKey];
    
    _cacheTimeoutInterval = timeoutInterval;
    
    
    if (!TTIsStringWithText(_serviceAddress))
        return;
    
    TTRelease(_responseData);
    TTRelease(_request);
    TTRelease(_parsedObject);
    
    [self webServiceDidStart];
    
    NSString *address = [self buildRequestAddressIfNeeded];
    TTDPRINT(@"%@", address);
    
    _request = [[TTHTTPRequest alloc] initWithURLAddress:address];
    _request.requestMethod = _requestMethod;
    [_request setRequestBody:[self buildRequestBodyIfNeeded]];
    if (_requestTimeoutInterval>0.0) _request.timeoutInterval = _requestTimeoutInterval;
    
    [_request addDelegate:self];
    [_request startAsynchronous];
}

- (void)cancel {
    _cancelled = YES;
    [_request cancel];
}

- (void)parse:(NSData *)data {
    
    if (!_cancelled) {
        
        // Prepare
        TTRelease(_parsedObject);
        id object = [self objectToBeParsed:data];
        
        
        if (!_cancelled) {
            
            if (TTIsInstance(object, [NSError class])) {
                [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService data invalid"]];
                return;
            } else {
                
                
                // Parse
                id result = [self parseObject:object];
                
                
                if (!_cancelled) {
                    
                    
                    if (TTIsInstance(result, [NSError class])) {
                        [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService parse failed"]];
                        return;
                    } else {
                        
                        
                        if (!_cancelled) {
                            
                            
                            // Save
                            TTRetain(result, _parsedObject);
                            [self saveResult:result];
                            
                            if (_cacheTimeoutInterval > 0) {
                                [_diskCache setString:@"web_service_up_to_date" forKey:_cacheKey withTimeoutInterval:_cacheTimeoutInterval];
                            }
                            
                            
                        }
                        
                    }
                    
                    
                }
                
                
            }
            
            
        }
        
        
    }
    
    if (_cancelled) {
        [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService cancelled"]];
    } else {
        [self webServiceWillFinish];
        [self webServiceDidFinish];
    }
    
    
    
    
    
    
    
//    if (_cancelled) {
//        [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService cancelled"]];
//        return;
//    }
//    
//    // Prepare
//    TTRelease(_parsedObject);
//    id object = [self objectToBeParsed:data];
//    
//    if (TTIsInstance(object, [NSError class])) {
//        _loading = NO;
//        [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService data invalid"]];
//        return;
//    }
//    
//    
//    
//    
//    if (_cancelled) {
//        [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService cancelled"]];
//        return;
//    }
//    
//    // Parse
//    id result = [self parseObject:object];
//    
//    if (TTIsInstance(result, [NSError class])) {
//        _loading = NO;
//        [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService parse failed"]];
//        return;
//    }
//    
//    
//    if (_cancelled) {
//        [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService cancelled"]];
//        return;
//    }
//    
//    // Save
//    TTRetain(result, _parsedObject);
//    [self saveResult:result];
//    
//    if (_cacheTimeoutInterval > 0) {
//        [_diskCache setString:@"web_service_up_to_date" forKey:_cacheKey withTimeoutInterval:_cacheTimeoutInterval];
//    }
//    
//    if (_cancelled) {
//        [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService cancelled"]];
//        return;
//    }
//    
//    
//    [self webServiceWillFinish];
//    _loading = NO;
//    [self webServiceDidFinish];
}

- (void)clearRequest {
    TTRelease(_request);
    TTRelease(_requestBody);
    [_parameters removeAllObjects];
}

- (void)clearResult {
    TTRelease(_responseData);
    TTRelease(_parsedObject);
}

- (void)clearAll {
    //TTRelease(_serviceAddress);
    TTRelease(_responseData);
    TTRelease(_request);
    //TTRelease(_requestMethod);
    //_requestMethod = [[NSString alloc] initWithString:@"GET"];
    TTRelease(_requestBody);
    
    TTRelease(_parsedObject);
    
    [_parameters removeAllObjects];
}

- (void)setNewParsedObject:(id)object {
    TTRetain(object, _parsedObject);
}


- (BOOL)isInCache {
    return [_diskCache hasCacheForKey:_cacheKey];
}

- (NSDate *)updateDate {
    return [_diskCache dateOfKey:_cacheKey];
}


- (TTPageManager *)defaultPageManager {
    return [self pageManagerForKey:@"default"];
}

- (TTPageManager *)pageManagerForKey:(NSString *)key {
    TTPageManager *pageManager = [_pageManagerMap objectForKey:key];
    if (pageManager == nil) {
        pageManager = [TTPageManager make];
        [self setPageManager:pageManager forKey:key];
    }
    return pageManager;
}

- (void)setPageManager:(TTPageManager *)pageManager forKey:(NSString *)key {
    if (_pageManagerMap == nil) _pageManagerMap = [[NSMutableDictionary alloc] init];
    [_pageManagerMap setObject:pageManager forKeyIfNotNil:key];
}

- (void)clearAllPageManager {
    [_pageManagerMap removeAllObjects];
}


- (void)webServiceAlreadyInCache {
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceAlreadyInCache:) withObject:self];
}

- (void)webServiceDidStart {
    _loading = YES;
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceDidStart:) withObject:self];
}

- (void)webServiceDidFail:(NSError *)error {
    _loading = NO;
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceDidFail:withError:) withObject:self withObject:error];
}

- (void)webServiceWillFinish {
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceWillFinish:) withObject:self];
}

- (void)webServiceDidFinish {
    _loading = NO;
    [[self delegates] makeObjectsRunSelectorOnMainThread:@selector(webServiceDidFinish:) withObject:self];
}


// To override
- (id)objectToBeParsed:(NSData *)data {
    return data;
}

- (id)parseObject:(id)object {
    return object;
}

- (void)saveResult:(id)result {
}

- (NSString *)stringValueForProperty:(NSString *)name {
    NSString *attribute = [[self class] attributeForProperty:name];
    if (TTIsStringWithText(attribute)) {
        
        if ([attribute hasPrefix:@"Tc"]) { // BOOL: Tc
            NSNumber *value = [self valueForKey:name];
            if (value == nil) value = [NSNumber numberWithBool:NO];
            if ([value boolValue]) return [NSString stringWithFormat:@"%d", 1];
            else return [NSString stringWithFormat:@"%d", 0];
        } else if ([attribute hasPrefix:@"Ti"]) { // int: Ti
            NSNumber *value = [self valueForKey:name];
            if (value == nil) value = [NSNumber numberWithInt:0];
            return [NSString stringWithFormat:@"%d", [value intValue]];
        } else if ([attribute hasPrefix:@"Tq"]) { // long long: Tq
            NSNumber *value = [self valueForKey:name];
            if (value == nil) value = [NSNumber numberWithLongLong:0];
            return [NSString stringWithFormat:@"%lld", [value longLongValue]];
        } else if ([attribute hasPrefix:@"Td"]) { // double: Td
            NSNumber *value = [self valueForKey:name];
            if (value == nil) value = [NSNumber numberWithDouble:0.0];
            return [NSString stringWithFormat:@"%f", [value doubleValue]];
        } else if ([attribute hasPrefix:@"T@\"NSString\""]) { // NSString: T@"NSString"
            NSString *value = [self valueForKey:name];
            if (!TTIsStringWithText(value)) value = @"";
            return value;
        } else if ([attribute hasPrefix:@"T@\"NSDate\""]) { // NSDate: T@"NSDate"
            NSDate *value = [self valueForKey:name];
            if (!TTIsInstance(value, [NSDate class])) value = [NSDate dateWithTimeIntervalSince1970:0.0];
            return [NSString stringWithFormat:@"%f", [value timeIntervalSince1970]];
        }
        
    }
    
    return nil;
}


#pragma mark - TTOperationDelegate

- (void)operationDidStart:(TTOperation *)operation {
}

- (void)operationUpdated:(TTOperation *)operation {
}

- (void)operationDidFail:(TTOperation *)operation withError:(NSError *)error {
    [self webServiceDidFail:[NSError errorWithDomain:TTTintErrorDomain description:@"WebService request failed"]];
}

- (void)operationWillFinish:(TTOperation *)operation {
}

- (void)operationDidFinish:(TTOperation *)operation {
    if (!_cancelled) {
        NSData *responseData = _request.responseData;
        TTRetain(responseData, _responseData);
        [self performSelector:@selector(parse:) onThread:[[self class] webServiceParseThread] withObject:_responseData waitUntilDone:NO];
    }
}


#pragma mark - TTDelegatesProtocol

- (NSMutableArray *)delegates {
    if (_delegates == nil) {
        _delegates = [[NSMutableArray weakArray] retain];
    }
    return _delegates;
}

- (id)addDelegate:(id)delegate {
    return [[self delegates] addNonEqualObjectIfNotNil:delegate];
}

- (void)removeDelegate:(id)delegate {
    [[self delegates] removeObject:delegate];
}

- (void)removeAllDelegates {
    [[self delegates] removeAllObjects];
}

@end



@implementation TTWebService (Private)


- (NSString *)buildRequestAddressIfNeeded {
    if ([_requestMethod isEqualToString:@"GET"]) {
        return [_serviceAddress stringByAddingQueryDictionary:_parameters];
    }
    return _serviceAddress;
}

- (NSData *)buildRequestBodyIfNeeded {
    if ([_requestMethod isEqualToString:@"POST"]) {
        if (TTIsInstance(_requestBody, [NSData class]) && [_requestBody length]>0) {
            return _requestBody;
        } else {
            NSData *parameterData = [[_parameters queryString] dataUsingEncoding:NSUTF8StringEncoding];
            if (TTIsDataWithBytes(parameterData)) {
                return parameterData;
            }
        }
    }
    return nil;
}

+ (NSThread *)webServiceParseThread {
    static NSThread *sharedParseThread = nil;
    if (sharedParseThread == nil) {
        sharedParseThread = [[NSThread alloc] initWithTarget:self 
                                                    selector:@selector(webServiceParseThreadEntryPoint:) 
                                                      object:nil];
        [sharedParseThread start];
    }
    return sharedParseThread;
}

+ (void)webServiceParseThreadEntryPoint:(id)object {
    while (YES) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        [[NSRunLoop currentRunLoop] run];
        [pool release];
    }
}

@end

